using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using Sedna.Core.Persistence.Hql;
using NHibernate;
using Nullables;
using Spring.Util;

namespace Sedna.Core.Persistence
{
    public class HqlQueryBuilder
    {
        private const string PARAM_PREFIX = "param";
        protected StringBuilder where = new StringBuilder();
        private IDictionary<string, object> parameters = new Dictionary<string, object>();
        private ArrayList subqueries = new ArrayList();
        private HqlClause select = new HqlClause();
        private HqlFromClause from = new HqlFromClause();
        private HqlOrderByClause orderBy = new HqlOrderByClause();
        private bool distinct = false;
        
//        private int subQueryCount = 0;

        public bool Distinct
        {
            get { return distinct; }
            set { distinct = value; }
        }

        public HqlClause Select
        {
            get { return select; }
        }

        public HqlFromClause From
        {
            get { return from; }
        }

        public HqlOrderByClause OrderBy
        {
            get { return orderBy; }
        }

//        public HqlSubquery CreateSubquery()
//        {
//            return new HqlSubquery(this, subQueryCount++);
//        }
        
        #region Where methods

        public HqlQueryBuilder Raw(string condition)
        {
            where.Append(" and ");
            where.Append(condition);
            return this;
        }

        public HqlQueryBuilder Raw(string condition, object value)
        {
            string paramName = AddParam(value);
            if (paramName == null)
            {
                return this;
            }
            where.Append(" and ");
            where.Append(String.Format(condition, ":"+paramName));
            return this;
        }

        public HqlQueryBuilder IsNull(string property)
        {
            where.Append(" and ")
                .Append(property)
                .Append(" is null");
            return this;
        }

        public HqlQueryBuilder IsNotNull(string property)
        {
            where.Append(" and ")
                .Append(property)
                .Append(" is not null");
            return this;
        }
        
        public HqlQueryBuilder In(string property, params object[] values)
        {
            return In(property, (ICollection) values);
        }
        
        public HqlQueryBuilder In(string property, ICollection values)
        {
            return Collection(property, values, true, true);
        }

        public HqlQueryBuilder In(string property, ICollection values, bool skipEmpty)
        {
            return Collection(property, values, true, skipEmpty);
        }

        public HqlQueryBuilder In(string property, HqlSubqueryBuilder subquery)
        {
            if (subquery == null)
                return this;
            where.Append(" and ")
                .Append(property)
                .Append(" in ({")
                .Append(subqueries.Add(subquery))
                .Append("})");
            return this;
        }

        public HqlQueryBuilder OrIn(params SubqueryEntry[] entries)
        {
            bool needOr = false;
            foreach (SubqueryEntry entry in entries)
            {
                if (entry.Property == null)
                    continue;
                where.Append(needOr ? " or " : " and (")
                    .Append(entry.Property)
                    .Append(" in ({")
                    .Append(subqueries.Add(entry.Subquery))
                    .Append("})");
                if (!needOr)
                    needOr = true;
            }
            if (needOr)
                where.Append(")");
            return this;
        }

        public HqlQueryBuilder NotIn(string property, HqlSubqueryBuilder subquery)
        {
            if (subquery == null)
                return this;
            where.Append(" and ")
                .Append(property)
                .Append(" not in ({")
                .Append(subqueries.Add(subquery))
                .Append("})");
            return this;
        }

        public HqlQueryBuilder NotIn(string property, params object[] values)
        {
            return NotIn(property, (ICollection)values);
        }
        
        public HqlQueryBuilder NotIn(string property, ICollection values)
        {
            return Collection(property, values, false, true);
        }
        
        public HqlQueryBuilder NotIn(string property, ICollection values, bool skipEmpty)
        {
            return Collection(property, values, false, skipEmpty);
        }

        public HqlQueryBuilder Between(string property, object fromValue, object toValue)
        {
            string condition;
            string fromParamName = AddParam(fromValue);
            string toParamName = AddParam(toValue);
            if (fromParamName != null && toParamName != null)
            {
                condition = " between :{0} and :{1}";
            }
            else if (fromParamName != null)
            {
                condition = ">=:{0}";
            }
            else if (toParamName != null)
            {
                condition = "<=:{1}";
            }
            else
            {
                return this;
            }
            where.Append(" and ")
            .Append(property)
            .AppendFormat(String.Format(condition, fromParamName, toParamName));
            return this;
        }

        public HqlQueryBuilder Like(string property, string value)
        {
            if (value==null)
            {
                return this;
            }
            value = value.Trim();
            if (String.Empty.Equals(value))
            {
                return this;
            }
            where.Append(" and ")
            .Append(property)
            .Append(" like :")
            .Append(AddParam("%" + value + "%"));
            return this;
        }

        public HqlQueryBuilder Lt(string property, object value)
        {
            string paramName = AddParam(value);
            if (paramName==null)
            {
                return this;
            }
            where.Append(" and ")
                .Append(property)
                .Append("<:")
                .Append(paramName);
            return this;
        }
        
        public HqlQueryBuilder Le(string property, object value)
        {
            string paramName = AddParam(value);
            if (paramName==null)
            {
                return this;
            }
            where.Append(" and ")
                .Append(property)
                .Append("<=:")
                .Append(paramName);
            return this;
        }
        
        public HqlQueryBuilder Gt(string property, object value)
        {
            string paramName = AddParam(value);
            if (paramName==null)
            {
                return this;
            }
            where.Append(" and ")
                .Append(property)
                .Append(">:")
                .Append(paramName);
            return this;
        }
        
        public HqlQueryBuilder Ge(string property, object value)
        {
            string paramName = AddParam(value);
            if (paramName==null)
            {
                return this;
            }
            where.Append(" and ")
                .Append(property)
                .Append(">=:")
                .Append(paramName);
            return this;
        }
        
        public HqlQueryBuilder Eq(string property, object value)
        {
            string paramName = AddParam(value);
            if (paramName == null)
            {
                return this;
            }
            where.Append(" and ")
            .Append(property)
            .Append("=:")
            .Append(paramName);
            return this;
        }

        public HqlQueryBuilder NotEq(string property, object value)
        {
            string paramName = AddParam(value);
            if (paramName == null)
            {
                return this;
            }
            where.Append(" and ")
            .Append(property)
            .Append("<>:")
            .Append(paramName);
            return this;
        }

        public HqlQueryBuilder OrEq(string property1, string property2, object value1, object value2)
        {
            string paramName1 = AddParam(value1);
            if (paramName1 == null)
            {
                return this;
            }
            string paramName2 = AddParam(value2);
            if (paramName2 == null)
            {
                return this;
            }

            where.Append(" and (")
            .Append(property1)
            .Append("=:")
            .Append(paramName1)
            .Append(" OR ")
            .Append(property2)
            .Append("=:")
            .Append(paramName2)
            .Append(")");
            return this;
        }
        
        public HqlQueryBuilder OrLike(string property1, string property2, string value1, string value2)
        {
            if (StringUtils.IsNullOrEmpty(value1) || StringUtils.IsNullOrEmpty(value2))
            {
                return this;
            }
            value1 = value1.Trim();
            value2 = value2.Trim();
            
            where.Append(" and (")
            .Append(property1)
            .Append(" like :")
            .Append(AddParam("%" + value1 + "%"))
            .Append(" OR ")
            .Append(property2)
            .Append(" like :")
            .Append(AddParam("%" + value2 + "%"))
            .Append(")");
            return this;
        }

        public HqlQueryBuilder OrBetween(string property1, object fromValue1, object toValue1, string property2, object fromValue2, object toValue2)
        {
            string condition1, condition2;
            string fromParamName1 = AddParam(fromValue1);
            string toParamName1 = AddParam(toValue1);
            string fromParamName2 = AddParam(fromValue2);
            string toParamName2 = AddParam(toValue2);
            if (fromParamName1 != null && toParamName1 != null)
            {
                condition1 = " between :{0} and :{1}";
            }
            else if (fromParamName1 != null)
            {
                condition1 = ">=:{0}";
            }
            else if (toParamName1 != null)
            {
                condition1 = "<=:{1}";
            }
            else
            {
                return this;
            }
            if (fromParamName2 != null && toParamName2 != null)
            {
                condition2 = " between :{0} and :{1}";
            }
            else if (fromParamName1 != null)
            {
                condition2 = ">=:{0}";
            }
            else if (toParamName2 != null)
            {
                condition2 = "<=:{1}";
            }
            else
            {
                return this;
            }
            where.Append(" and ( ( ")
                .Append(property1)
                .AppendFormat(String.Format(condition1, fromParamName1, toParamName1))
                .Append(" ) OR ( ")
                .Append(property2)
                .AppendFormat(String.Format(condition2, fromParamName2, toParamName2))
                .Append(" ) ")
                //.AppendFormat(String.Format("OR ({0}='' OR {1}='')",property1,property2))
                .Append(" )");
            return this;
        }

        #endregion
        
        protected virtual string GetNextParamName()
        {
            return PARAM_PREFIX + parameters.Count;
        }

        #region Private methods

        internal protected virtual string AddParam(object param)
        {
            param = ExtractValue(param);
            if (param==null||String.Empty.Equals(param))
            {
                return null;
            }
            string paramName = GetNextParamName();
            parameters[paramName] = param;
            return paramName;
        }
        
        private object ExtractValue(object param)
        {
            if (param==null||String.Empty.Equals(param))
            {
                return null;
            }
            INullableType nullable = param as INullableType;
            if (nullable!=null)
            {
                return  nullable.HasValue?nullable.Value:null;
            }
            Type type = param.GetType();
            if (type.IsGenericType&&type.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
            {
                param = type.GetProperty("Value").GetValue(param, new object[] {});
            }
            return param;
        }
        
        private void SetParam(IQuery query, string name, object value)
        {
            if (value == null) return;

//            if (value is Enum)
//            {
//                query.SetEnum(name, (Enum)value);
//                return;
//            }
//
//            if (value is Int32)
//            {
//                query.SetInt32(name, (int)value);
//                return;
//            }
//
//            if (value is string)
//            {
//                query.SetString(name, (string)value);
//                return;
//            }
//
//            if (value is DateTime)
//            {
//                query.SetDateTime(name, (DateTime)value);
//                return;
//            }

            query.SetParameter(name, value);
        }

        private HqlQueryBuilder Collection(string property, ICollection values, bool isIn, bool skipEmpty)
        {
            if (values == null || (values.Count == 0&&skipEmpty))
            {
                return this;
            }
            ArrayList paramNames = new ArrayList(values.Count);
            foreach (object value in values)
            {
                string paramName = AddParam(value);
                if (paramName != null)
                {
                    paramNames.Add(paramName);
                }
            }
            where.Append(" and ")
                .Append(property)
                .Append( isIn ?  " in (" : " not in (");

            bool addComma = false;
            foreach (string paramName in paramNames)
            {
                if (addComma)
                {
                    where.Append(",");
                }
                where.Append(":").Append(paramName);
                addComma = true;
            }
            where.Append(")");
            return this;
        }
        #endregion

        public IQuery ToQuery(ISession session)
        {
            IQuery query = session.CreateQuery(ToHql());
            foreach (KeyValuePair<string, object> param in parameters)
            {
                SetParam(query, param.Key, param.Value);
            }
            return query;
        }

        protected String ToHql()
        {
            StringBuilder sb = new StringBuilder();
            select.Build(distinct ? "select distinct" : "select", sb);
            from.Build(sb);

            if (where.Length > 0)
            {
                sb.Append(" where");
            }

            if (where.Length > 0)
            {
                sb.Append(where.ToString(4, where.Length - 4));
            }

            orderBy.Build("order by", sb);
            if (subqueries.Count > 0)
            {
                String result = sb.ToString();
                result = String.Format(result, subqueries.ToArray());
                //for (int i = 0; i < subqueries.Count; i++ )
                //{
                //    result = String.Format(result, subqueries[i]);
                //}
                return result;
            }
            return sb.ToString();
        }
        
        public class SubqueryEntry
        {
            private string property;
            private HqlSubqueryBuilder subquery;

            public string Property
            {
                get { return property; }
            }

            public HqlSubqueryBuilder Subquery
            {
                get { return subquery; }
            }

            public SubqueryEntry(string property, HqlSubqueryBuilder subquery)
            {
                this.property = property;
                this.subquery = subquery;
            }
        }
    }

}
